package com.lemon.exam.common.authentication;

import com.lemon.exam.common.entity.system.LoginUser;
import com.lemon.exam.common.exception.CustomException;
import jakarta.annotation.security.PermitAll;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.server.PathContainer;
import org.springframework.security.access.annotation.Secured;
import org.springframework.security.access.prepost.PostAuthorize;
import org.springframework.security.access.prepost.PostFilter;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.security.access.prepost.PreFilter;
import org.springframework.security.authorization.AuthorizationDecision;
import org.springframework.security.authorization.ReactiveAuthorizationManager;
import org.springframework.security.core.Authentication;
import org.springframework.security.web.server.authorization.AuthorizationContext;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.reactive.result.method.annotation.RequestMappingHandlerMapping;
import org.springframework.web.util.pattern.PathPattern;
import org.springframework.web.util.pattern.PathPatternParser;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

import java.lang.reflect.Method;
import java.util.List;

/**
 * 全局权限验证
 *
 * @author Lemon
 * @since 2025/3/26 11:50
 */
@Slf4j
public class GlobalAuthorityAuthorizationManager implements ReactiveAuthorizationManager<AuthorizationContext> {
    private final RequestMappingHandlerMapping requestMappingHandlerMapping;
    private final PathPatternParser pathPatternParser;

    public GlobalAuthorityAuthorizationManager(RequestMappingHandlerMapping requestMappingHandlerMapping) {
        this.requestMappingHandlerMapping = requestMappingHandlerMapping;
        this.pathPatternParser = new PathPatternParser();
    }

    @Override
    @Deprecated//会被废弃，所有check方法调用 verify方法替代
    public Mono<AuthorizationDecision> check(Mono<Authentication> authentication, AuthorizationContext object) {
        return verify(authentication, object)
                .thenReturn(new AuthorizationDecision(true))
                .onErrorReturn(new AuthorizationDecision(false));
    }

    @Override
    public Mono<Void> verify(Mono<Authentication> authentication, AuthorizationContext object) {
        return requestMappingHandlerMapping.getHandler(object.getExchange())
                .map(handler -> ((HandlerMethod) handler).getMethod())
                .flatMap(method -> {
                    if (hasSecurityAnnotation(method)) {
                        return Mono.empty(); // 已有注解则跳过全局校验
                    }
                    return authentication
                            .filter(Authentication::isAuthenticated)
                            .switchIfEmpty(Mono.error(new CustomException(401, "未认证")))
                            .map(auth -> (LoginUser) auth.getPrincipal())
                            .flatMap(loginUser -> verifyAccess(object, loginUser.getResourceList()));
                });
    }

    /**
     * 验证访问权限
     *
     * @param context
     * @param permittedResources
     * @return
     */
    private Mono<Void> verifyAccess(AuthorizationContext context, List<String> permittedResources) {
        return Flux.fromIterable(permittedResources)
                .map(this::compilePattern)
                .filter(pattern -> pattern.matches(PathContainer.parsePath(context.getExchange().getRequest().getPath().value().split("\\?")[0])))
                .next()
                .switchIfEmpty(Mono.error(new CustomException(403, "权限不足")))
                .then();
    }

    /**
     * 编译URI模式
     *
     * @param uriPattern
     * @return
     */
    private PathPattern compilePattern(String uriPattern) {
        // 处理Ant风格通配符
        String pattern = uriPattern
                .replace("**", "*") // PathPattern使用*作为通配符
                .replace("/*/", "/{*}/"); // 中间路径通配

        return pathPatternParser.parse(pattern);
    }

    /**
     * 判断是否包含权限注解
     *
     * @param method
     * @return
     */
    private boolean hasSecurityAnnotation(Method method) {
        return method.getAnnotation(PreAuthorize.class) != null ||
                method.getAnnotation(PreFilter.class) != null ||
                method.getAnnotation(PostAuthorize.class) != null ||
                method.getAnnotation(PostFilter.class) != null ||
                method.getAnnotation(PermitAll.class) != null;
    }
}
